package com.github.xzb617.adm.service.impl;

import com.github.xzb617.adm.dao.FlowMapper;
import com.github.xzb617.adm.service.FlowParamService;
import com.github.xzb617.adm.service.FlowRuleService;
import com.github.xzb617.adm.service.FlowService;
import com.github.xzb617.adm.service.FlowStatusService;
import com.github.xzb617.client.config.ClientsTransmitter;
import com.github.xzb617.client.config.key.ClientKeyType;
import com.github.xzb617.client.flow.dto.HotParamRule;
import com.github.xzb617.client.flow.props.HotParamProperties;
import com.github.xzb617.client.flow.props.RejectProperties;
import com.github.xzb617.client.flow.props.RulesProperties;
import com.github.xzb617.client.flow.props.SystemProtectedProperties;
import com.github.xzb617.domain.common.PageTextCondition;
import com.github.xzb617.domain.dto.FlowDTO;
import com.github.xzb617.domain.dto.FlowInfoDTO;
import com.github.xzb617.domain.entity.Flow;
import com.github.xzb617.domain.entity.FlowParam;
import com.github.xzb617.domain.entity.FlowRule;
import com.github.xzb617.except.ServiceException;
import com.github.xzb617.security.Subject;
import com.github.xzb617.security.SubjectHolder;
import com.github.xzb617.utils.ObjectMapUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class FlowServiceImpl implements FlowService {

    private final FlowMapper flowMapper;
    private final FlowRuleService flowRuleService;
    private final FlowParamService flowParamService;
    private final FlowStatusService flowStatusService;
    private final ClientsTransmitter clientsTransmitter;

    public FlowServiceImpl(FlowMapper flowMapper, FlowRuleService flowRuleService, FlowParamService flowParamService, FlowStatusService flowStatusService, ClientsTransmitter clientsTransmitter) {
        this.flowMapper = flowMapper;
        this.flowRuleService = flowRuleService;
        this.flowParamService = flowParamService;
        this.flowStatusService = flowStatusService;
        this.clientsTransmitter = clientsTransmitter;
    }

    @Override
    public Flow getById(Integer id) {
        return this.flowMapper.selectByPrimaryKey(id);
    }

    @Override
    public Flow getByName(String flowName) {
        Example example = new Example(Flow.class);
        example.createCriteria()
                .andEqualTo("flowName", flowName)
                .andEqualTo("status", true);
        return this.flowMapper.selectOneByExample(example);
    }

    @Override
    public Map<String, Object> getClientFlowConfig(String flowName) {
        // 查询Flow
        Flow flow = this.getByName(flowName);
        if (flow == null) {
            return null;
        }
        Integer flowId = flow.getId();
        // 查询FlowRule
        List<FlowRule> flowRules = this.flowRuleService.getListByFlowId(flowId);
        // 查询FlowParam
        List<FlowParam> flowParams = this.flowParamService.getListByFlowId(flowId);
        // 转换模型
        return this.convertToFlowProperties(flow, flowRules, flowParams);
    }

    @Override
    public List<Flow> getList(PageTextCondition condition) {
        Example example = new Example(Flow.class);
        Example.Criteria criteria = example.createCriteria();
        if (condition.isNotEmptyOrNull()) {
            criteria.andLike("flowName", condition.like())
                    .orLike("remark", condition.like());
        }
        return this.flowMapper.selectByExample(example);
    }

    @Override
    @Transactional
    public void update(FlowDTO dto) {
        // 更新 Flow
        Flow flow = dto.getFlow();
        this.flowMapper.updateByPrimaryKeySelective(flow);
        // 更新 FlowRule
        this.flowRuleService.update(flow.getId(), dto.getRules());
        // 更新 FlowParam
        this.flowParamService.update(flow.getId(), dto.getParams());
        // 新增一条配置更新记录到状态表，提供给客户端的定时查询逻辑
        this.flowStatusService.saveOrUpdateStatus(flow.getFlowName());
        // 实时下发到客户端
        this.clientsTransmitter.transmitClient(ClientKeyType.FLOW, flow.getFlowName());
    }

    @Override
    @Transactional
    public void saveInfo(FlowInfoDTO dto) {
        Flow flow = this.getByName(dto.getFlowName());
        if (flow != null) {
            throw new ServiceException("限流规则名已被使用");
        }
        flow = new Flow();
        BeanUtils.copyProperties(dto, flow);
        // 默认的拒绝策略
        flow.setRejectStrategy("QUICK_FAIL");
        flow.setRejectResponseStatus(429);
        flow.setRejectResponseContent("Blocking by GoalKeeper");
        flow.setSysCpu(100);
        flow.setSysMemory(100);
        flow.setCreateTime(new Date());
        flow.setCreateUser(SubjectHolder.getContext().getUsername());
        // 新增
        this.flowMapper.insertSelective(flow);
    }

    @Override
    @Transactional
    public void updateInfo(FlowInfoDTO dto) {
        // 规则是否存在
        Flow flow = this.getById(dto.getId());
        if (flow == null) {
            throw new ServiceException("限流规则不存在");
        }
        // 名称是否已被使用
        Flow var = this.getByName(dto.getFlowName());
        if (var!=null && !var.getId().equals(flow.getId())) {
            throw new ServiceException("限流规则名已被使用");
        }
        // 赋值
        BeanUtils.copyProperties(dto, flow, "id");
        flow.setUpdateTime(new Date());
        flow.setUpdateUser(SubjectHolder.getContext().getUsername());
        // 更新
        this.flowMapper.updateByPrimaryKeySelective(flow);
    }

    @Override
    @Transactional
    public void deleteById(Integer id) {
        // 规则是否存在
        Flow flow = this.getById(id);
        if (flow == null) {
            throw new ServiceException("限流规则不存在");
        }
        // 删除Flow
        this.flowMapper.deleteByPrimaryKey(id);
        // 删除FlowRule
        this.flowRuleService.deleteByFlowId(id);
        // 删除FlowParam
        this.flowParamService.deleteByFlowId(id);
    }

    private Map<String, Object> convertToFlowProperties(Flow flow, List<FlowRule> flowRules, List<FlowParam> flowParams) {
        Map<String, Object> flowConfigMap = new HashMap<>();
        // system
        SystemProtectedProperties system = new SystemProtectedProperties();
        Map<String, Integer> sysRules = new HashMap<>(3);
        if (flow.getSysQps() != null) {
            sysRules.put("qps", flow.getSysQps());
        }
        if (flow.getSysCpu() != null) {
            sysRules.put("cpu", flow.getSysCpu());
        }
        if (flow.getSysMemory() != null) {
            sysRules.put("memory", flow.getSysMemory());
        }
        system.setRules(sysRules);
        // reject
        RejectProperties reject = new RejectProperties();
        if (flow.getRejectStrategy() != null) {
            reject.setStrategy(flow.getRejectStrategy());
        }
        if (flow.getRejectResponseStatus() != null) {
            reject.setResponseStatus(flow.getRejectResponseStatus());
        }
        if (flow.getRejectResponseContent() != null) {
            reject.setResponseContent(flow.getRejectResponseContent());
        }
        // rules
        RulesProperties rules = new RulesProperties();
        Map<String, Map<String, Integer>> limits = rules.getLimits();
        Map<String, Integer> ipMap = new HashMap<>();
        Map<String, Integer> urlMap = new HashMap<>();
        for (FlowRule flowRule : flowRules) {
            if ("IP".equalsIgnoreCase(flowRule.getRuleType())) {
                // ip含有 '.'
                String ruleKey = flowRule.getRuleKey();
                if (ruleKey.contains(".")) {
                    ruleKey = "[" + ruleKey + "]";
                }
                ipMap.put(ruleKey, flowRule.getRuleQps());
            }
            if ("URL".equalsIgnoreCase(flowRule.getRuleType())) {
                // url可能含有 '/'
                String ruleKey = flowRule.getRuleKey();
                if (ruleKey.contains("/")) {
                    ruleKey = "[" + ruleKey + "]";
                }
                urlMap.put(ruleKey, flowRule.getRuleQps());
            }
        }
        limits.put("IP", ipMap);
        limits.put("URL", urlMap);
        // params
        HotParamProperties param = new HotParamProperties();
        Map<String, HotParamRule> paramRules = param.getRules();
        for (FlowParam flowParam : flowParams) {
            String methodUrl = flowParam.getMethodUrl();
            if (methodUrl.contains("/")) {
                methodUrl = "[" + methodUrl + "]";
            }
            paramRules.put(methodUrl, convertToHotParamRule(flowParam));
        }

        // 组合配置
        Map<String, Object> systemMap = ObjectMapUtil.objToMap("goalkeeper.flow.system", system);
        Map<String, Object> rejectMap = ObjectMapUtil.objToMap("goalkeeper.flow.reject", reject);
        Map<String, Object> rulesMap = ObjectMapUtil.objToMap("goalkeeper.flow.rules", rules);
        Map<String, Object> paramMap = ObjectMapUtil.objToMap("goalkeeper.flow.param", param);

        flowConfigMap.putAll(systemMap);
        flowConfigMap.putAll(rejectMap);
        flowConfigMap.putAll(rulesMap);
        flowConfigMap.putAll(paramMap);

        return flowConfigMap;
    }

    private HotParamRule convertToHotParamRule(FlowParam flowParam) {
        HotParamRule rule = new HotParamRule();
        rule.setRuleName(flowParam.getMethodUrl());
        rule.setParamType(flowParam.getParamType());
        rule.setParamName(flowParam.getParamName());
        rule.setParamValue(flowParam.getParamValue());
        rule.setRate(flowParam.getQps());
        rule.setStartTime(flowParam.getStartTime());
        rule.setFinishTime(flowParam.getFinishTime());
        return rule;
    }
}
